/* Copyright (c) Microsoft Corporation.
   Licensed under the MIT License. */

/***************************************************************************

	Actor Sound.

	Primary Author: ******
	Status:  Reviewed
		
***************************************************************************/

#include "soc.h"

ASSERTNAME


/***************************************************************************
	
	Add a sound event to the actor event list, and create an undo object

***************************************************************************/
bool ACTR::FSetSnd(PTAG ptag, bool fLoop, bool fQueue, bool fMotionMatch, long vlm, long sty)
{
	AssertThis(0);
	AssertVarMem(ptag);
	AssertIn(sty, 0, styLim);
	
	PACTR pactrDup;

	if (!FDup(&pactrDup))
		{
		return fFalse;
		}

    if (!FSetSndCore(ptag, fLoop, fQueue, fMotionMatch, vlm, sty))
		{
		Restore(pactrDup);
        ReleasePpo(&pactrDup);
		return fFalse;
		}

	if (!FCreateUndo(pactrDup, fTrue))
		{
		Restore(pactrDup);
        ReleasePpo(&pactrDup);
		return fFalse;
		}

	ReleasePpo(&pactrDup);
	return fTrue;
}


/***************************************************************************
	
	Add a sound event to the actor event list
	For user sounds, the chid must also be fetched for the argument *ptag
	and stored with the event, since for user sounds, the cno can change
	(eg on scene import)
	Note: ptag is current because the sound browser has just completed building
	a current tag.	

***************************************************************************/
bool ACTR::FSetSndCore(PTAG ptag, bool fLoop, bool fQueue, bool fMotionMatch, long vlm, long sty)
{
	AssertThis(0);
	AssertVarMem(ptag);
	AssertIn(sty, 0, styLim);

	PMSND pmsnd = pvNil;
	AEVSND aevsnd;
	long ccel;
	long iaev;
	AEV *paev;
	long sqn;
	long cbVar;

	// Verify sound before including in the event list
	pmsnd = (PMSND)vptagm->PbacoFetch(ptag, MSND::FReadMsnd);
	if (pvNil == pmsnd)
		goto LFail;
	
	if (pmsnd->Sty() == styMidi)
		{
		// There are no midi actor sounds in the product
		PushErc(ercSocNoActrMidi);
		goto LFail;
		}

	// Verify space up front
	cbVar = kcbVarStep + kcbVarSnd;
	if (!_pggaev->FEnsureSpace(2, cbVar, fgrpNil))
		return fFalse;
		
	aevsnd.fLoop = fLoop;
	aevsnd.fQueue = fQueue;
	aevsnd.vlm = (vlm == vlmNil) ? pmsnd->Vlm() : vlm;
	aevsnd.tag = *ptag;
	aevsnd.fNoSound = pmsnd->FNoSound();
	aevsnd.sty = (sty == styNil) ? pmsnd->Sty() : sty;
	sty = aevsnd.sty;
	vlm = aevsnd.vlm;

	if (ptag->sid != ksidUseCrf)
		TrashVar(&aevsnd.chid);
	else
		{
		if (!_pscen->Pmvie()->FChidFromUserSndCno(aevsnd.tag.cno, &aevsnd.chid))
			goto LFail;
		}

	if (!fMotionMatch)
		aevsnd.celn = smmNil;	// Not a motion match sound	  	
	else
		{
		if (!_ptmpl->FGetCcelActn(_anidCur, &ccel))
			goto LFail;
		aevsnd.celn = _celnCur % ccel;
		}

	if (!_FAddDoAev(aetSnd, kcbVarSnd, &aevsnd))
		goto LFail;

	ReleasePpo(&pmsnd);

	// Remove "no sounds" of this sqn between here and the next
	// sound of this sqn
	sqn = MSND::SqnActr(sty, _arid);
	for (iaev = _iaevCur; iaev < _pggaev->IvMac(); iaev++)
		{
		AEVSND aevsndT;
		paev = (AEV *)_pggaev->QvFixedGet(iaev);
		if (aetAdd == paev->aet)
			break;
		if (aetSnd != paev->aet)
			continue;
		_pggaev->Get(iaev, &aevsndT);
		if (MSND::SqnActr(aevsndT.sty, _arid) != sqn)
			continue;
		
		// Quit when reach real sound of same sqn
		if (!aevsndT.fNoSound)
			break;	
		_RemoveAev(iaev);
		iaev--;
		}


	// Enqueue the motion match sounds in the Msq
	// Allow the edit even if all sounds do not play
	if (!(_pscen->GrfScen() & fscenSounds))
		_FEnqueueSmmInMsq();		//Ignore errors : other sounds involved

	Pscen()->Pmvie()->Pmcc()->SetSndFrame(fTrue);
	return fTrue;

LFail:
	ReleasePpo(&pmsnd);
	return fFalse;
}


/***************************************************************************
	
	Enqueue the sound at event iaev.
	Enter motion match sounds in the smm gl.
	Enter non-motion match sounds in the Msq

***************************************************************************/
bool ACTR::_FEnqueueSnd(long iaev)
{
	AssertThis(0);
	AssertIn(iaev, 0, _pggaev->IvMac());

	AEVSND aevsnd;
	PMSND pmsnd = pvNil;
	long tool;

	_pggaev->Get(iaev, &aevsnd);

	//
	// Motion match sounds are not enqueued when event iaev is seen
	// Otherwise the sound would be played twice
	//
	if (aevsnd.celn != smmNil)
		{
		// Insert Smm for this cel in the gl
		return _FInsertSmm(iaev);
		}
	
	if (aevsnd.fLoop)
		tool = toolLooper;
	else
		tool = toolSounder;

	// If the scene was imported, the sound will need to be resolved
	if (aevsnd.tag.sid == ksidUseCrf)
		{
		if (!_pscen->Pmvie()->FResolveSndTag(&aevsnd.tag, aevsnd.chid))
			{
			Bug("Actrsnd: Expected to resolve snd tag");
			goto LFail;
			}
		_pggaev->Put(iaev, &aevsnd);	// Update event
		}

 	pmsnd = (PMSND)vptagm->PbacoFetch(&aevsnd.tag, MSND::FReadMsnd);
	if (pvNil == pmsnd)
		goto LFail;

	if (!_pscen->Pmvie()->Pmsq()->FEnqueue(pmsnd, _arid, aevsnd.fLoop, aevsnd.fQueue,
		aevsnd.vlm, pmsnd->Spr(tool), fTrue))
		{
		goto LFail;
		}
	
	ReleasePpo(&pmsnd);
	return fTrue;

LFail:
	ReleasePpo(&pmsnd);
	return fFalse;
}


/***************************************************************************
	
	Play the motion match sounds appropriate to the current cel

***************************************************************************/
bool ACTR::_FEnqueueSmmInMsq(void)
{
	AssertThis(0);

	long celn;
	long ccel;
	long ismm;
	SMM *psmm;
	bool fSuccess = fTrue;
	PMSND pmsnd = pvNil;

	if (!_ptmpl->FGetCcelActn(_anidCur, &ccel))
		return fFalse;
	celn = _celnCur % ccel;

	for (ismm = 0; ismm < _pglsmm->IvMac(); ismm++)
		{
		psmm = (SMM *)(_pglsmm->QvGet(ismm));
		Assert(psmm->aevsnd.celn != smmNil, "Logic error in smm");
		if (psmm->aevsnd.celn != celn)
			{
			continue;
			}

		if (psmm->aevsnd.tag.sid == ksidUseCrf)
			{
			if (!_pscen->Pmvie()->FResolveSndTag(&psmm->aevsnd.tag, psmm->aevsnd.chid))
				{
				Bug("Actrsnd: Expected to resolve snd tag");
				goto LFail;
				}
			_pglsmm->Put(ismm, psmm);	// Update event
			}

		pmsnd = (PMSND)vptagm->PbacoFetch(&psmm->aevsnd.tag, MSND::FReadMsnd);
		if (pvNil == pmsnd)
			{
			fSuccess = fFalse;
			continue;
			}

	  	// Motion match sounds need to be entered at low priority as sounds of the
		// same type take precedence over them
		if (!_pscen->Pmvie()->Pmsq()->FEnqueue(pmsnd, _arid, psmm->aevsnd.fLoop,
			psmm->aevsnd.fQueue, psmm->aevsnd.vlm, pmsnd->Spr(toolMatcher), fTrue, 0, fTrue))
			{
			// Continuing would result in additional error
			goto LFail;
			}

		ReleasePpo(&pmsnd);
		}

	return fSuccess;
LFail:
	ReleasePpo(&pmsnd);
	return fFalse;
}


/***************************************************************************
	
	Insert a motion match sound in the smm queue

***************************************************************************/
bool ACTR::_FInsertSmm(long iaev)
{
	AssertThis(0);
	AssertIn(iaev, 0, _pggaev->IvMac());

	long ismm;
	SMM smm;
	SMM *psmm;
	AEV *paev;
	AEVSND *paevsnd;

	paev = (AEV *)_pggaev->QvFixedGet(iaev);
	paevsnd = (AEVSND *)_pggaev->QvGet(iaev);

	smm.aev = *paev;
	smm.aevsnd = *paevsnd;

	for (ismm = 0; ismm < _pglsmm->IvMac(); ismm++)
		{
		psmm = (SMM *)_pglsmm->QvGet(ismm);
		if (psmm->aevsnd.celn != paevsnd->celn)
			continue;
		if (psmm->aevsnd.sty != paevsnd->sty)
			continue;

		// Replace the current smm with the new
		_pglsmm->Put(ismm, &smm);
		return fTrue;
		}

	// No sound for this cel was found
	// Add a new mm sound
	return _pglsmm->FAdd(&smm);
}


/***************************************************************************
	
	Delete old motion sounds in event list

***************************************************************************/
bool ACTR::_FRemoveAevMm(long anid)
{
	AssertThis(0);

	long iaev;
	AEV *paev;
	AEVSND aevsnd;
	AEVACTN aevactn;

	// Remove motion match sounds from the previous action
	for (iaev = _iaevCur; iaev < _pggaev->IvMac(); iaev++)
		{
		paev = (AEV *)_pggaev->QvFixedGet(iaev);
		if (aetAdd == paev->aet)
			break;
		if (aetActn == paev->aet)
			{
			_pggaev->Get(iaev, &aevactn);
			if (aevactn.anid != anid)
				break;
			continue;
			}
		if (aetSnd != paev->aet)
			continue;

		// Test if sound is motion match
		_pggaev->Get(iaev, &aevsnd);
		if (smmNil == aevsnd.celn)
			continue;
		_RemoveAev(iaev);
		iaev--;
		}

	return fTrue;
}

/***************************************************************************
	
	Insert default motion match sounds in event list

***************************************************************************/
bool ACTR::_FAddAevDefMm(long anid)
{
	AssertThis(0);
	TAG tag;
	PMSND pmsnd;
	long iceln;
	long ccel;
	long vlm;
	long sty;
	bool fSoundExists;

	// Insert new motion match sounds
	if (!_ptmpl->FGetCcelActn(anid, &ccel))
		return fFalse;

	for (iceln = 0; iceln < ccel; iceln++)
		{
		if (!_ptmpl->FGetSndActnCel(anid, iceln, &fSoundExists, &tag))
			continue;		//Ignore failure
		if (!fSoundExists)
			continue;

		pmsnd = (PMSND)vptagm->PbacoFetch(&tag, MSND::FReadMsnd);
		if (pvNil == pmsnd)
			continue;  		//Ignore failure
	
		vlm = pmsnd->Vlm();
		sty = pmsnd->Sty();

		if (!FSetSndCore(&tag, fFalse, vlm, fFalse,	sty, fTrue))
			{
			goto LFail;		// Continuing pointless
			}

		ReleasePpo(&pmsnd);
		}

	 return fTrue;

LFail:
	ReleasePpo(&pmsnd);
	return fFalse;
}


/***************************************************************************
	
	Set the Volume for sounds in the current frame of the specified sty

***************************************************************************/
bool ACTR::FSetVlmSnd(long sty, bool fMotionMatch, long vlm)
{
	AssertThis(0);
	AssertIn(sty, 0, styLim);
	AssertIn(vlm, 0, kvlmFull + 1);

	AEVSND aevsnd;
	bool fActnThisFrame = fFalse;
	long celn;
	long ccel;
	long iaev;
	long ismm;
	AEV aev;
	SMM smm;
	long nfrmMM = ivNil;

	if (!_ptmpl->FGetCcelActn(_anidCur, &ccel))
		return fFalse;

	celn = _celnCur % ccel;

	// Set the volume for any events in the actor list for this frame
	for (iaev = _iaevFrmMin; iaev < _iaevCur; iaev++)
		{
		_pggaev->GetFixed(iaev, &aev);
		if (aev.aet == aetActn)
			fActnThisFrame = fTrue;

		if (aev.aet != aetSnd)
			continue;
		
		_pggaev->Get(iaev, &aevsnd);
		if (FPure(aevsnd.celn != smmNil) != fMotionMatch)
		   	continue;

		if (aevsnd.sty != sty)
			continue;
		aevsnd.vlm = vlm;
		_pggaev->Put(iaev, &aevsnd);		
		}

	// Set the volume for any earlier motion match sounds
	// which land in this cel
	if (fMotionMatch && !fActnThisFrame)
		{
		for (iaev = _iaevFrmMin - 1; iaev > 0; iaev--)
			{
			_pggaev->GetFixed(iaev, &aev);

			// Quit when the action changes
			if (aev.aet == aetActn || (nfrmMM != ivNil && nfrmMM > aev.nfrm))
				break;

			if (aev.aet != aetSnd)
				continue;
			_pggaev->Get(iaev, &aevsnd);

			if (aevsnd.sty != sty)
				continue;

			// Only adjust sounds that match this cel
			if (aevsnd.celn != celn)
				continue;
			aevsnd.vlm = vlm;
			_pggaev->Put(iaev, &aevsnd);
			nfrmMM = aev.nfrm;
			// There are no queued motion match sounds by spec
			break;
			}
		}
	
	if (fMotionMatch)
		{
		// Adjust the volume in the smm also
		for (ismm = 0; ismm < _pglsmm->IvMac(); ismm++)
			{
			_pglsmm->Get(ismm, &smm);
			Assert(smm.aevsnd.celn != smmNil, "Logic error in smm");

			if (smm.aevsnd.celn != celn)
				continue;
		
			if (smm.aevsnd.sty != sty)
				continue;

			// Set the volume in the smm
			smm.aevsnd.vlm = vlm;
			_pglsmm->Put(ismm, &smm);
			}
		}

	_pscen->MarkDirty();
	return fTrue;	
}


/***************************************************************************
	
	Query actor for current frame sounds

***************************************************************************/
bool ACTR::FQuerySnd(long sty, bool fMotionMatch, PGL *pglTagSnd, long *pvlm, bool *pfLoop)
{
	AssertThis(0);
	AssertIn(sty, 0, styLim);
	AssertVarMem(pvlm);
	AssertVarMem(pfLoop);

	PGL pgltag = pvNil;
	AEVSND aevsnd;
	long ccel;
	long celn;
	AEV aev;
	long iaev;
	long ismm;
	SMM smm;

	if (pvNil == (pgltag = GL::PglNew(size(TAG), kctagSndGrow)))
		return fFalse;
	pgltag->SetMinGrow(kctagSndGrow);

	if (!fMotionMatch)
		{
		// Non-motion match sounds
		for (iaev = _iaevFrmMin; iaev < _iaevCur; iaev++)
			{
			_pggaev->GetFixed(iaev, &aev);
			if (aev.aet != aetSnd)
				continue;
		
			_pggaev->Get(iaev, &aevsnd);
			if (aevsnd.sty != sty)
				continue;

			// Handle motion match sounds later
			if (aevsnd.celn != smmNil)
				continue;
			if (aevsnd.tag.sid == ksidUseCrf)
				{
				if (!_pscen->Pmvie()->FResolveSndTag(&aevsnd.tag, aevsnd.chid))
					goto LFail;
				}
			if (!pgltag->FAdd(&aevsnd.tag))
				goto LFail;
			*pvlm = aevsnd.vlm;
			*pfLoop = aevsnd.fLoop;
			}
		}

	if (fMotionMatch)
		{
		if (!_ptmpl->FGetCcelActn(_anidCur, &ccel))
			goto LFail;

		celn = _celnCur % ccel;

		// Motion match sounds
		for (ismm = 0; ismm < _pglsmm->IvMac(); ismm++)
			{
			_pglsmm->Get(ismm, &smm);
			if (smm.aevsnd.sty != sty)
				continue;
			if (smm.aevsnd.celn != celn)
				continue;
			if (smm.aevsnd.tag.sid == ksidUseCrf)
				{
				if (!_pscen->Pmvie()->FResolveSndTag(&smm.aevsnd.tag, smm.aevsnd.chid))
					goto LFail;
				}
			if (!pgltag->FAdd(&smm.aevsnd.tag))
				goto LFail;
			*pvlm = smm.aevsnd.vlm;
			*pfLoop = smm.aevsnd.fLoop;
			break;
			}
		}

	if (pgltag->IvMac() == 0)
		{
		ReleasePpo(&pgltag);
		*pglTagSnd = pvNil;
		return fTrue;
		}

	*pglTagSnd = pgltag;
	return fTrue;

LFail:
	ReleasePpo(&pgltag);
	return fFalse;

}


/***************************************************************************
	
	Delete a sound in this frame

***************************************************************************/
bool ACTR::FDeleteSndCore(long sty, bool fMotionMatch)
{
	AssertThis(0);
	AssertIn(sty, 0, styLim);

	AEVSND aevsnd;
	long iaevFirst;
	long iaev;
	long ismm;
	long ccel;
	long celn;
	AEV aev;
	SMM smm;
	long nfrmMM = ivNil;		// For motion match, this may be an earlier frame

	// First the actor event list
	if (!_ptmpl->FGetCcelActn(_anidCur, &ccel))
		return fFalse;
	celn = _celnCur % ccel;

	iaevFirst = (fMotionMatch) ? _iaevActnCur : _iaevFrmMin;
	for (iaev = _iaevCur - 1; iaev >= iaevFirst; iaev--)
		{
		_pggaev->GetFixed(iaev, &aev);
		if (fMotionMatch && nfrmMM != ivNil && nfrmMM > aev.nfrm)
			break;

		if (aev.aet != aetSnd)
			continue;
		
		_pggaev->Get(iaev, &aevsnd);
		if (aevsnd.sty != sty)
			continue;
 		if ((aevsnd.celn != smmNil) != fMotionMatch)
		   	continue;

		// Only delete the sound that match this cel
		if (fMotionMatch && aevsnd.celn != celn)
			continue;

		// Delete this sound & continue to remove any
		// other sounds which are possibly chained
		_RemoveAev(iaev);
		nfrmMM = aev.nfrm;
		}

	
	if (fMotionMatch)
		{
	  	// Also the motion match sound list : smm
		if (!_ptmpl->FGetCcelActn(_anidCur, &ccel))
			return fFalse;
		celn = _celnCur % ccel;

		// Motion match sounds
		for (ismm = 0; ismm < _pglsmm->IvMac(); ismm++)
			{
			_pglsmm->Get(ismm, &smm);
			if (smm.aevsnd.sty != sty)
				continue;
			if (smm.aevsnd.celn != celn)
				continue;
			_pglsmm->Delete(ismm);
			ismm--;
			}
		}

	_pscen->MarkDirty();
	return fTrue;
}


/******************************************************************************
	FSoundInFrm
		Enumerates all actor events for current frame looking for a sound
		event.
	
	Returns: fTrue if a sound event was found, fFalse otherwise
	
************************************************************ PETED ***********/
bool ACTR::FSoundInFrm(void)
{
	AssertThis(0);
	AssertPo(Pscen(), 0);

	/* Can't provide useful info if we're not at the current scene frame */
	if (Pscen()->Nfrm() != _nfrmCur)
		return fFalse;

	for (long iaev = _iaevFrmMin; iaev < _iaevCur; iaev++)
		{
		AEV aev;

		_pggaev->GetFixed(iaev, &aev);
		if (aev.aet == aetSnd)
			return fTrue;
		}
	return fFalse;
}

/******************************************************************************
	Resolve all sound tags
	
******************************************************************************/
bool ACTR::FResolveAllSndTags(CNO cnoScen)
{
	AssertThis(0);
	long iaev;
	AEV *paev;
	AEVSND aevsnd;
	bool fSuccess = fTrue;

	for (iaev = 0; iaev < _pggaev->IvMac(); iaev++)
		{
		paev = (AEV *)_pggaev->QvFixedGet(iaev);
		if (paev->aet != aetSnd)
			continue;
		_pggaev->Get(iaev, &aevsnd);
		if (aevsnd.tag.sid == ksidUseCrf)
			{
			if (!_pscen->Pmvie()->FResolveSndTag(&aevsnd.tag, aevsnd.chid, cnoScen))
				{
				fSuccess = fFalse;
				continue;
				}
			_pggaev->Put(iaev, &aevsnd);
			}
		}
	return fSuccess;
}
